#light
namespace Demos.ParserWeb

(* Uncomment this to get intelisense in VS
#I "Bin"
#r "webtools.core.dll"
#r "webtools.controls.dll"
//*)

open System
open System.Text
open FSharp.WebTools
open FSharp.WebTools.Core
open FSharp.WebTools.ClientLibrary
open FSharp.WebTools.ClientControls
open Demos.ParserWeb

// ----------------------------------------------------------------------------
// Represents behavior logic of the page
// This type mixes client & server-side code using monads

[<MixedSide>]
type Parser = 
  inherit ClientPage as base

  // --------------------------------------------------------------------------
  // Controls defined in the "ASPX" markup 
  
  val mutable txtPlotFrom : TextBox
  val mutable txtPlotTo : TextBox
  val mutable txtPlotVar : TextBox
  val mutable txtDiff : TextBox
  val mutable txtInput : TextBox
  val mutable lblFormated : Label
  val mutable lblEvaluated : Label
  val mutable lblMsg : Label
  val mutable lblPlotMsg : Label
  val mutable lblDiffVars : Label
  val mutable lblDiff : Label
  val mutable imgPlot : Image
  
  new() = 
    let fnull () = (Array.zero_create 1).[0]
    { txtInput    = fnull(); imgPlot     = fnull(); lblEvaluated = fnull(); 
      txtDiff     = fnull(); lblFormated = fnull(); lblMsg       = fnull(); 
      lblDiff     = fnull(); lblDiffVars = fnull(); lblPlotMsg   = fnull(); 
      txtPlotFrom = fnull(); txtPlotTo   = fnull(); txtPlotVar   = fnull(); 
      simpleAst   = fnull(); } 

  // --------------------------------------------------------------------------
  // Server-side 
  
  /// Initialization - 
  /// register client-side (JavaScript) class using 'RegisterClientClass'
  override this.OnLoad (e:System.EventArgs) =
    this.RegisterClientClass(type Parsing);
    if (this.IsClearRequest) then
      execute (this.imgPlot.set_ClientVisible(false))
    base.OnLoad(e);


  // --------------------------------------------------------------------------
  // Client-side
  
  /// 'Special' member - executed when the client-side page loads
  [<ReflectedDefinition>]
  member this.Client_Load (sender:obj, e:EventArgs) =
    client
      { // Set default values
        do this.txtInput.ClientText    <- "sqrt(sin(x*15)+2)*cos(x+(4*2)-5+x/(5-4))"
        do this.txtDiff.ClientText     <- "x"
        do this.txtPlotVar.ClientText  <- "x"
        do this.txtPlotFrom.ClientText <- (0.0).ToString()
        do this.txtPlotTo.ClientText   <- (2.0).ToString()
        
        // Setup event handlers
        do this.txtInput.ClientKeyUp.AddClient(this.TextChanged) 
        do this.txtDiff.ClientKeyUp.AddClient(this.DiffVarChanged) 
        
        // Invoke initial parsing & rendering ...
        do! this.TextChanged(sender,e)
        
        // Start asynchronous 'process' that refreshes the image
        do! asyncExecute
             (this.RefreshImage
                    ("", this.txtPlotFrom.ClientText, this.txtPlotTo.ClientText)); }  
  
  // --------------------------------------------------------------------------
  // Parsing & differentiation, etc..
  
  [<WebBehavior(WebBehavior.ClientSide)>]
  val mutable simpleAst : AstNode
  
  /// Display all variables from the expression
  [<ReflectedDefinition>]
  member this.DisplayVars(vars:ResizeArray<string>) =
    client
      { let sb = new StringBuilder()
        do for i=0 to vars.Count-1 do
             if (i<>0) then ignore(sb.Append(", "))
             ignore(sb.Append(vars.[i]))
        let str = sb.ToString()
        do! this.lblDiffVars.set_ClientText(str) }


  /// Performs all processing of the AST node (on the client side)
  /// including differentiation, simplification and pretty-printing
  [<ReflectedDefinition>]      
  member this.Process(ast:AstNode) =
    client
      { let expr = this.txtInput.ClientText
        let html = Parsing.PrettyPrint(ast)
        let simple = Parsing.Simplify(ast)
        let evalhtml = Parsing.PrettyPrint(simple)
        
        do  this.simpleAst <- simple
        do! this.DisplayVars(Parsing.GetVariables(simple))
        do! this.DiffVarChanged(this, new EventArgs())
        
        do! this.lblMsg.set_ClientText("OK");
        do! this.lblFormated.set_ClientText(html);
        do! this.lblEvaluated.set_ClientText(evalhtml); }


  /// Differentiation settings changed - re-run the differentiation
  [<ReflectedDefinition>]
  member this.DiffVarChanged (sender:obj, e:ClientLibrary.EventArgs) = 
    client
      { let vn = this.txtDiff.ClientText
        let df = Parsing.Differentiate(this.simpleAst, vn)
        let dh = Parsing.PrettyPrint(Parsing.Simplify(df))
        do! this.lblDiff.set_ClientText(dh) }
  
  
  /// Input box value changed - run parsing and call 'Process' client method
  [<ReflectedDefinition>]
  member this.TextChanged (sender:obj, e:ClientLibrary.EventArgs) = 
    client
      { do! this.lblMsg.set_ClientText("Incorrect input!");
        let ast = ref None
        do! ClientLibrary.Internal.ClientCore.TryCatch
             ((fun () ->
                (client
                  { let tokens = Parsing.Tokenize(this.txtInput.ClientText);         
                    let ast = Parsing.Parse(tokens)
                    do! this.lblMsg.set_ClientText("Ok")
                    do! this.Process(ast) })), 
              (fun () -> 
                (client { -> () }) ))}
  
  
  // --------------------------------------------------------------------------
  // Image rendering
  
  // Server-side function that calls rendering and returns URL of the image
  member this.GenerateImage (ast, rfrom:float, rto:float) = 
    server 
      { -> try
             let name = Renderer.Render(ast, rfrom, rto)
             "getimage.aspx?id=" + name
           with _ ->
             "" }
             

  /// Client-side asynchronous function that checks for changes of the text
  /// and invokes rendering on the server if needed
  [<ReflectedDefinition>]
  member this.RefreshImage(last, lfrom, lto) =
    client_async
      { do! Async.SleepAsync(100)
        let current = this.txtInput.ClientText
        let cfrom = this.txtPlotFrom.ClientText
        let cto = this.txtPlotTo.ClientText
        if (last <> current || lfrom <> cfrom || lto <> cto) then
          
          // Asynchronous call to the server-side code
          let  rfrom = Double.Parse(cfrom) 
          let  rto   = Double.Parse(cto)
          let! url = serverExecute 
                      (this.GenerateImage(this.simpleAst, rfrom, rto))
                      
          // Process result from the server
          if url <> "" then
            do! this.imgPlot.set_ClientVisible(true)
            do! this.lblPlotMsg.set_ClientText("Rendered function '"+current+"'.")
            do  this.imgPlot.ClientImageUrl <- url
            do! this.RefreshImage(current, cfrom, cto)
          else
            do! this.lblPlotMsg.set_ClientText("Drawing of '"+current+"' failed!") 
            do! this.RefreshImage(current, cfrom, cto) 
        else
          do! this.RefreshImage(current, cfrom, cto) }
